///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef __BUOLA_IO_CSOCKET_H__
#define __BUOLA_IO_CSOCKET_H__

#include <buola/buola.h>
#include <buola/utility/usignal.h>
#include <buola/io/cfdstream.h>
#include <buola/io/cpacket.h>
#include <buola/io/standard.h>

#include <sys/socket.h>

namespace buola { namespace io {

///\addtogroup network
///@{

///////////////////////////////////////////////////////////////////////

/// stream to do IO to/from sockets

/// it is also used to create connection sockets
///////////////////////////////////////////////////////////////////////

DECLARE_NAME(FromFD);

class CSocket : public CFDStream
{
public:
//! \name constructor and destructor
//@{
    CSocket(NFromFD,int pFD,ESocketType pType)
        :   CFDStream(pFD,true)
        ,   mSocketType(pType)
    {
        msg_info() << "created socket " << (int)mSocketType << "\n";
    }
    
    CSocket(const CSocket &pSocket)=delete;
    CSocket &operator=(const CSocket &pSocket)=delete;
//@}

public:
    ESocketType SocketType()     {   return mSocketType; }
//@}

//! \name receiving and sending data
//@{
    ssize_t Receive(void *pDest,int pLen,int pFlags=0)
    {
        ssize_t lRet=::recv(FD(),pDest,pLen,pFlags);

        if(lRet<0)
        {
            if(errno==EAGAIN)
                return -1;
            else if(errno==EINTR)
                return Receive(pDest,pLen,pFlags);
            else if(errno==ECONNRESET)
                return 0;
            
            throw XIO("error in recv: "+c_error_string(errno));
        }

        return lRet;
    }

    ssize_t ReceiveNB(void *pDest,int pLen)
    {
        return Receive(pDest,pLen,MSG_DONTWAIT);
    }

    ssize_t ReceivePacket(void *pDest,int pLen,int pFlags=0)
    {
        //we should probably use recvmsg, and check for EOR, but it seems that AF_UNIX doesn't set EOR, so
        //we just do a normal receive. when we implement sctp, we can check if we have to change the behaviour
        return Receive(pDest,pLen,pFlags);
    }

    ssize_t ReceivePacketNB(void *pDest,int pLen)
    {
        return ReceivePacket(pDest,pLen,MSG_DONTWAIT);
    }

    ssize_t ReceivePacketWithSocket(void *pDest,int pLen,PSocket &pSocket,int pFlags=0);
    ssize_t ReceivePacketWithSocketNB(void *pDest,int pLen,PSocket &pSocket)
    {
        return ReceivePacketWithSocket(pDest,pLen,pSocket,MSG_DONTWAIT);
    }

    ssize_t Send(const void *pSrc,int pLen,int pFlags=0)
    {
#if BUOLA_PLATFORM_LINUX
        ssize_t lRet=::send(FD(),pSrc,pLen,pFlags|MSG_NOSIGNAL);
#else //BUOLA_PLATFORM_LINUX
        ssize_t lRet=::send(FD(),pSrc,pLen,pFlags);
#endif

        if(lRet==pLen) return pLen;
        
        if(lRet==0)
        {
            throw XIO("EOF in Send");
        }
        else if(lRet<0)
        {
            if(errno==EAGAIN)
                return -1;
            else if(errno==EINTR)
                return Send(pSrc,pLen,pFlags);
            
            throw XIO("error in send: "+c_error_string(errno));
        }

        size_t lNewRet=Send((const char*)pSrc+lRet,pLen-lRet,pFlags);
        if(lNewRet==-1) return -1;
        return lRet+lNewRet;
    }

    ssize_t SendPacket(const void *pSrc,int pLen)
    {
#if BUOLA_PLATFORM_LINUX
        //EOR seems a good idea, but AF_UNIX just seems to ignore it
        return Send(pSrc,pLen,MSG_EOR);
#else
        return Send(pSrc,pLen);
#endif        
    }

    //!sends the packet, and passes the socket in the ancillary data
    ssize_t SendPacketWithSocket(const void *pSrc,int pLen,PSocket pSocket);

    ssize_t Send(const std::string &pString)
    {
        return Send(pString.data(),pString.length());
    }
    
    void Send(const CPacketBase &pPacket)
    {
        if(pPacket.PassSocket())
            SendPacketWithSocket(pPacket.Data(),pPacket.Size(),pPacket.PassSocket().get());
        else
            SendPacket(pPacket.Data(),pPacket.Size());
    }
//@}

private:
    ESocketType mSocketType;
};

template<typename tEndPoint>
PSocket connect_to(const tEndPoint &pEndPoint,ESocketType pType=ESocketType::NONE)
{
    PSocket lReturn=pEndPoint.GetConnectedSocket(pType);
    
#if BUOLA_PLATFORM_MAC
    int lOn=1;
    setsockopt(lReturn->GetFD(),SOL_SOCKET,SO_NOSIGPIPE,&lOn,sizeof(lOn));
#endif //BUOLA_PLATFORM_MAC

    return lReturn;
}

std::pair<PSocket,PSocket> socket_pair(ESocketType pType=ESocketType::NONE);

///@}

/*namespace io*/ } /*namespace buola*/ }

#endif
